1. Introduction to State Management in Mobile Apps
State management is the process of storing, updating, and sharing information that determines how an application behaves and what appears on the screen. In React Native, state may represent a user's login status, shopping cart, selected theme, form input, product list, navigation preferences, or data retrieved from a remote API.
As a mobile application grows, state can become difficult to manage across multiple components and screens. A small app may work well with React's useState hook, while a larger app may benefit from useReducer, Context API, Redux Toolkit, Zustand, or a server-state library such as TanStack Query.
There is no single state management solution that is best for every application. The correct choice depends on the type of state, the number of components that use it, the complexity of updates, caching requirements, debugging needs, and the team's experience.
1.1 Learning Objectives
Understand the meaning and importance of state management.
Identify local, shared, global, server, and persistent state.
Understand React Native's built-in state management options.
Choose a state management solution according to project requirements.
Understand state updates, immutability, and component re-rendering.
Manage authentication, themes, forms, shopping carts, and API data.
Understand persistence, performance, testing, and debugging.
Apply practical state management patterns in a React Native application.
2. What Is State?
State is information that can change over time and affect an application's behavior or rendered interface. When state changes through React's supported mechanisms, React can render the affected components with updated information.
2.1 Examples of State in Mobile Applications
Application Feature
Example State
Possible Approach
Login screen
Email, password visibility, validation errors
useState or a form library
Theme settings
Light or dark theme
Context API or a global store
Shopping cart
Products, quantities, subtotal
useReducer, Context, Zustand, or Redux Toolkit
Product catalogue
Products retrieved from a server
TanStack Query or another server-state solution
Authentication
Current user and session status
Context or a global store, with secure token handling
Navigation UI
Selected tab or open drawer
Local state or navigation state
App preferences
Language, display settings, selected options
Local state plus persistence when needed
2.2 Why State Management Matters
Consistent UI: Components display information that reflects the current state.
Data sharing: Multiple screens can access shared information.
Maintainability: Related updates can be organized in predictable places.
Performance: Appropriate state placement can avoid unnecessary re-rendering.
Testing: Predictable updates are easier to verify.
Debugging: Clear state transitions help developers understand unexpected behavior.
Persistence: Selected state can be restored when the application restarts.
3. Understanding Different Types of State
Before selecting a library, identify what type of information the application needs to manage. Different categories have different requirements and should not automatically be placed in one global store.
3.1 Local Component State
Local state belongs to one component or a small group of closely related components. It is suitable for temporary interface information.
Examples include:
Whether a modal is open.
The value of a text input.
Whether a password is visible.
The currently selected item in a small component.
import React, { useState } from "react";
import { Button, Text, View } from "react-native";
export default function Counter() {
const [count, setCount] = useState(0);
return (
Count: {count}
This example uses useState because the counter value is needed only by the component itself.
3.2 Shared Component State
Shared state is information required by multiple related components. The state can often be moved to their closest common parent and passed down through props.
For example, a product filter component and a product list may both need the selected category. The parent can own the category state and pass the value and update function to each child.
3.3 Global Application State
Global state is information used across unrelated parts of the application. Examples include selected application theme, shared authentication status, or a shopping cart used by several screens.
Global state can be managed with Context API, Redux Toolkit, Zustand, or another appropriate store. However, globalizing every state variable can make an application harder to understand and test.
3.4 Server State
Server state is information owned by a remote service. Examples include product lists, user profiles, notifications, and order histories.
Server data can become stale because another user or service may update it. Server-state tools such as TanStack Query can help with fetching, caching, refetching, invalidation, and request status management.
3.5 Persistent State
Persistent state is information that should remain available after an application closes or restarts. Examples include language preference, selected theme, or non-sensitive settings.
Persistence is separate from the in-memory state management choice. A React state hook or global store does not automatically save values to device storage.
3.6 Navigation State
Navigation state represents the current route, navigation history, and navigation parameters. Navigation libraries such as React Navigation manage this information. Avoid duplicating navigation state in a separate global store unless there is a specific, justified requirement.
4. The State Management Decision Process
The best starting point is the simplest solution that meets the application's current needs. Add complexity only when the requirements justify it.
4.1 Step-by-Step Selection Process
Identify the data: Determine whether it is temporary UI state, shared client state, server data, navigation state, or persistent preferences.
Identify the consumers: Check how many components and screens need the information.
Review update complexity: Decide whether changes are simple assignments or complex transitions involving several related values.
Evaluate remote data requirements: Determine whether caching, background refresh, retries, or invalidation are necessary.
Consider persistence: Decide which values must survive an app restart and where they should be stored.
Consider debugging and testing: Evaluate how easily developers can trace state changes and test behavior.
Measure performance: Identify real re-rendering or data-loading bottlenecks rather than optimizing prematurely.
Choose and document the approach: Explain which state belongs where and why.
4.2 Quick Decision Guide
Requirement
Recommended Starting Point
A value used by one component
useState
Several related state transitions
useReducer
A small number of shared values across a component tree
Context API
Complex shared client state and structured update logic
Redux Toolkit
A lightweight shared store with a simple API
Zustand
Remote data that needs caching and synchronization
TanStack Query or a similar server-state library
Simple preferences that survive app restarts
State plus suitable device storage
Form validation and complex form workflows
Local state or a dedicated form library
5. Managing State with useState
The useState hook is built into React and is appropriate for many simple state management tasks. It returns the current state value and a setter function that schedules an update.
5.1 Basic Syntax
const [value, setValue] = useState(initialValue);
value: The current state value.
setValue: The function used to request a state update.
This is an example of local state. The value belongs to one form component and does not require a global state library.
5.3 Updating State Correctly
When the next state depends on the previous value, use the functional updater form. It ensures the update is calculated from the latest queued state value.
setCount((previousCount) => previousCount + 1);
For objects and arrays, create a new object or array rather than mutating the existing state directly.
Supports objects, arrays, strings, numbers, booleans, and other values.
5.5 Limitations of useState
Many related state variables can become difficult to coordinate.
Sharing state across distant screens can require additional architecture.
It does not provide a global cache for server data.
It does not automatically persist state across application restarts.
6. Managing Complex State with useReducer
The useReducer hook is useful when state updates involve several related values or when an application has multiple predictable transitions. It centralizes update logic in a reducer function.
6.1 Reducer Concepts
State: The current collection of values.
Action: An object describing what happened.
Reducer: A function that calculates the next state from the current state and action.
Dispatch: A function used to send an action to the reducer.
This example uses useReducer to manage related cart transitions. A production cart would also calculate quantities, validate prices using trusted server data, handle stock restrictions, and synchronize checkout operations with the backend.
6.3 When Should You Use useReducer?
Several state values change together.
Updates follow defined actions or transitions.
State logic is becoming difficult to maintain across multiple setters.
You want reducer logic that can be tested independently.
7. Sharing State with the Context API
Context API allows a component to provide a value to descendants without passing it through every intermediate component as props. It is useful for shared values such as theme, locale, or a current-user context.
import React, { createContext, useContext, useState } from "react";
import { Button, Text, View } from "react-native";
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [darkMode, setDarkMode] = useState(false);
const value = {
darkMode,
toggleTheme: () =>
setDarkMode((current) => !current)
};
return (
{children}
);
}
function ThemeScreen() {
const theme = useContext(ThemeContext);
return (
Current theme: {theme.darkMode ? "Dark" : "Light"}
The provider exposes a shared theme value and a function to change it. In a complete application, the provider would normally wrap the relevant part of the component tree and theme styles would be organized consistently.
7.3 Advantages of Context API
Built into React.
Reduces prop drilling for values needed by distant descendants.
Useful for themes, localization, and shared configuration.
Can be combined with useReducer for more structured state transitions.
7.4 Limitations of Context API
It is not a complete replacement for every state management requirement.
Consumers may re-render when the provided value changes.
Frequently changing values may require careful provider design.
It does not automatically provide server-state caching or request deduplication.
Too many providers can make the application structure harder to follow if poorly organized.
8. Managing Global State with Redux Toolkit
Redux Toolkit is the recommended standard approach for writing Redux logic. It provides utilities for creating slices, reducers, actions, and stores while reducing common boilerplate.
8.1 When Redux Toolkit Is Appropriate
The application has complex shared client state.
Multiple screens depend on coordinated state updates.
Developers need a predictable state transition model.
State changes benefit from Redux DevTools and established debugging workflows.
The project has multiple developers who need consistent state conventions.
8.2 Installing Redux Toolkit
npm install @reduxjs/toolkit react-redux
Use the installation instructions appropriate to your React Native project and build setup.
Predictable state transitions and organized reducer logic.
Useful debugging and developer tooling.
Good support for larger teams and complex applications.
Includes createAsyncThunk and can integrate with RTK Query for asynchronous workflows and server data.
8.8 Limitations of Redux Toolkit
Requires store configuration and additional concepts.
Can be unnecessary for small applications with little shared state.
Developers must understand slices, reducers, selectors, and dispatching.
Server-state caching needs an appropriate approach, such as RTK Query, rather than treating every remote request as ordinary client state.
9. Managing Shared State with Zustand
Zustand is a lightweight state management library that provides a store and hooks for reading or updating shared state. It can be useful when an application needs shared state without adopting Redux's full structure.
The selectors access only the parts of the store that the component needs. Selector design and equality behavior should be considered when optimizing re-renders.
9.4 Advantages of Zustand
Small API and relatively little boilerplate.
Suitable for shared state used across screens.
Supports selecting specific pieces of state.
Can be combined with middleware for persistence and development workflows.
9.5 Limitations of Zustand
Requires an additional dependency.
Projects still need clear conventions for store organization and update logic.
Persistence and server-state synchronization require deliberate configuration.
Large teams may prefer more explicit architectural conventions provided by other tools.
10. Managing Server State with TanStack Query
Remote API data has different requirements from ordinary local state. It may need caching, refetching, background updates, retries, and invalidation after a mutation. TanStack Query is designed to manage these server-state concerns.
10.1 When to Use TanStack Query
Several screens use the same API data.
Data needs to be refreshed when it becomes stale.
Requests should be cached or deduplicated.
Mutations need to update or invalidate cached queries.
Loading and error states should be managed consistently.
10.2 Installing the Library
npm install @tanstack/react-query
10.3 Configuring QueryClientProvider
import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ProductsScreen from "./ProductsScreen";
const queryClient = new QueryClient();
export default function App() {
return (
);
}
10.4 Fetching Products with useQuery
import React from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { useQuery } from "@tanstack/react-query";
async function fetchProducts() {
const response = await fetch(
"https://dummyjson.com/products"
);
if (!response.ok) {
throw new Error("Unable to fetch products.");
}
return response.json();
}
export default function ProductsScreen() {
const {
data,
isPending,
isError,
error,
refetch
} = useQuery({
queryKey: ["products"],
queryFn: fetchProducts
});
if (isPending) {
return ;
}
if (isError) {
return (
{error.message}
refetch()}>Tap to retry
);
}
return (
{data.products.map((product) => (
{product.title}
))}
);
}
This example demonstrates basic query usage. For a production application, use a Pressable or Button for retry interactions and FlatList for long product lists. Query behavior and status names can vary by library version, so check the documentation for the installed version.
10.5 Understanding Query Keys
A query key uniquely identifies a query's cached data. Include relevant parameters when they change the result.
Including productId in the key helps the library distinguish one product's cached result from another product's result.
10.6 Updating Data with Mutations
Use mutations for server operations such as creating, updating, or deleting records. After a successful mutation, invalidate or update the relevant cached queries so the UI reflects the latest server data.
This is a pattern snippet. It assumes useQueryClient and useMutation are imported, updateProduct is implemented, and the component is inside QueryClientProvider.
11. Comparing Popular State Management Approaches
Approach
Main Purpose
Setup
Best Fit
Key Limitation
useState
Local component state
Minimal
Simple UI and form values
Not a shared global store
useReducer
Structured local state transitions
Minimal
Complex component workflows
Does not automatically share state globally
Context API
Share values through a component tree
Low
Theme, locale, shared configuration
No built-in server cache
Redux Toolkit
Structured shared client state
Moderate
Complex applications and teams
More concepts and setup
Zustand
Lightweight shared state
Low to moderate
Shared app state with a small API
Requires project conventions
TanStack Query
Remote server-state management
Moderate
API data, caching, refetching
Not intended to replace all client state
RTK Query
API fetching and caching in Redux Toolkit
Moderate
Redux-based applications with substantial API usage
Best fit when adopting its Redux-based model
12. State Management for Authentication
Authentication usually involves more than a boolean indicating whether a user is logged in. The application may need to represent initialization, an authenticated session, an unauthenticated session, and an authentication failure.
Modeling the status explicitly prevents the app from briefly displaying the login screen before it has checked whether a saved session is available.
12.2 Choosing an Approach
Context API may be sufficient for a small application with simple shared authentication state.
Redux Toolkit or Zustand may fit applications with more complex shared client state.
A server-state library can manage remote user-profile data, but it does not replace secure credential handling.
Use secure storage appropriate to the platform and sensitivity of tokens.
Enforce authorization on the server; hiding a screen in the client is not a security boundary.
12.3 Important Authentication Practices
Do not hardcode passwords or private API keys in the application.
Do not store sensitive tokens in unencrypted AsyncStorage.
Handle expired sessions and unauthorized responses consistently.
Clear relevant user-specific cached data when logging out.
Ensure navigation responds correctly to changes in authentication state.
13. State Management for Shopping Carts
A shopping cart often contains several related values: product IDs, quantities, item prices, discounts, and calculated totals. It is a good example of state that may need to be shared across multiple screens.
13.1 Choosing a Cart Approach
Small prototype: useReducer or Context API may be sufficient.
Lightweight app-wide store: Zustand may be a good fit.
Large application with complex shared state: Redux Toolkit may be appropriate.
Server-owned cart: Use API mutations and a server-state strategy to keep the displayed cart synchronized.
13.2 Store Derived Values Carefully
Totals can usually be calculated from cart items instead of being stored independently. This avoids inconsistencies when item quantities change.
const total = cartItems.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
For real purchases, the backend must validate prices, discounts, inventory, and the final payable amount. A client-calculated total is for display and is not a trusted payment value.
14. State Management for Forms
Forms may require values, validation errors, touched fields, submission status, and server validation messages. Small forms can use useState, while complex forms may benefit from a dedicated form library.
Validation rules are complex or dependent on other fields.
Fields need touched, dirty, and error tracking.
The form includes dynamic arrays or nested objects.
Consistent submission and validation behavior is needed across screens.
Libraries such as React Hook Form can help organize form behavior, but their use should depend on the project's complexity and compatibility requirements.
15. State Persistence in React Native
Most React state exists in memory and is lost when the app process restarts. Persist only the information that needs to survive restarts, and decide how the application should restore it.
15.1 Common Persistence Options
Storage Type
Suitable Data
Important Consideration
AsyncStorage
Non-sensitive preferences and simple cached values
Unencrypted; not for secrets
Secure storage
Sensitive credentials or tokens when appropriate
Choose a maintained solution suitable for the target platforms
SQLite or another database
Structured local records and larger datasets
Requires schema and data lifecycle management
Server-side storage
Account-owned information that must synchronize across devices
Requires authentication, authorization, and connectivity handling
15.2 Persisting Non-Sensitive Preferences
import AsyncStorage from
"@react-native-async-storage/async-storage";
async function saveTheme(theme) {
await AsyncStorage.setItem("theme", theme);
}
async function loadTheme() {
return AsyncStorage.getItem("theme");
}
Install and configure AsyncStorage using its official instructions. Handle storage failures and provide a sensible default if no saved preference exists.
15.3 Persistence Best Practices
Persist only necessary data.
Version or migrate stored data when the data structure changes.
Handle corrupt, missing, or outdated values safely.
Do not assume that cached server data is always current.
Clear user-specific persisted information on logout when required.
Use encrypted or platform-backed secure storage for sensitive credentials.
16. Performance and Re-Rendering
State changes can cause components to render again. The goal is not to prevent all re-renders but to keep them correct and efficient.
16.1 Keep State Close to Where It Is Used
Local state often reduces the number of components affected by a change. Avoid placing every temporary input value in a global store.
16.2 Split Contexts When Appropriate
When unrelated values change at different rates, separating them into different contexts can reduce unnecessary context-driven re-renders.
16.3 Use Selectors
Global stores often provide selectors that let a component subscribe to a specific part of the state. Select only the values the component needs.
16.4 Avoid Premature Optimization
Profile performance before making complex changes.
Use FlatList for large lists.
Keep state updates immutable and predictable.
Use memoization only when it provides a measurable benefit.
Choose appropriate cache policies for remote data.
17. Common State Management Mistakes
Mistake
Why It Is a Problem
Better Practice
Making every value global
Creates unnecessary coupling.
Keep local state local unless sharing is required.
Using Context for all API caching
Requires custom cache and refetch logic.
Consider a server-state library for remote data.
Mutating state directly
Can lead to missed or incorrect updates.
Create new objects and arrays or use supported immutable update tools.
Duplicating derived values
Stored values can become inconsistent.
Calculate values from their source state when practical.
Persisting sensitive data insecurely
Exposes credentials to unnecessary risk.
Use appropriate secure storage.
Storing all server responses forever
Data may become stale or consume excess memory.
Define cache freshness and invalidation rules.
Using multiple stores without a plan
Creates unclear ownership of state.
Document which tool owns each category of data.
Choosing a library only because it is popular
Adds complexity without solving a real problem.
Choose based on actual requirements.
Ignoring logout cleanup
Another session may see stale user-specific data.
Clear or invalidate the relevant state and cache.
18. Testing State Management
State management should be tested at the level where its behavior is defined. Reducers and pure state transformations can often be tested without rendering a screen. Components and API workflows need additional integration tests.
18.1 What to Test
Initial state is correct.
Actions produce the expected next state.
Invalid or unexpected actions are handled safely.
Loading, success, and error states render correctly.
API failures do not leave the interface stuck in loading.
Logout clears user-specific information as intended.
This small example illustrates testing a pure state transition. In an actual test suite, use your project's testing framework and assert expected results rather than relying only on console output.
19. Example Application Architecture
A maintainable application assigns each type of state a clear owner instead of putting every value into one tool.
This is an illustrative structure. A small app may not need every folder, and the exact structure depends on the state management libraries and architecture chosen.
19.2 Example Ownership Plan
Data
Possible Owner
Reason
Open modal
Component useState
Only the current component needs it.
Theme
Context or a small global store
Many screens may need the same value.
Cart contents
Reducer, Zustand, or Redux Toolkit
Multiple screens may update shared cart state.
Product catalogue
TanStack Query or RTK Query
Remote data benefits from caching and refetching.
Current navigation route
Navigation library
The router owns navigation transitions.
Theme preference across restarts
Theme state plus persistent storage
In-memory state alone does not survive restarts.
20. Practical Mini Project: State Management for a Shopping App
20.1 Project Objective
Build a small React Native shopping application that displays products, manages a shared cart, remembers a theme preference, and loads product data from an API.
20.2 Functional Requirements
Create a product listing screen.
Retrieve products from a remote API.
Display loading, success, error, and empty states.
Allow users to add and remove products from a cart.
Display the cart count in a header or tab.
Calculate the subtotal from cart items.
Share theme information across relevant screens.
Persist the theme preference between app launches.
Handle API failures with a retry option.
Test logout and cleanup behavior if authentication is implemented.
20.3 Suggested State Choices
Feature
Suggested Approach
Reason
Search field
useState
Local interaction state.
Product data
TanStack Query or RTK Query
Remote data and caching.
Cart
useReducer, Zustand, or Redux Toolkit
Shared state and related updates.
Theme
Context API or global store
Shared application preference.
Saved theme
AsyncStorage or another appropriate preference store
Persistence across restarts.
Navigation
Navigation library
Owns route transitions.
20.4 Project Evaluation Checklist
State ownership is clear and documented.
Local UI state is not unnecessarily global.
Remote API data is not duplicated across several stores without a reason.
Updates are predictable and do not mutate state directly.
Loading and error handling are consistent.
Persistent data is appropriate and stored securely where required.
Components render correctly when state changes.
Tests cover normal behavior and failure cases.
21. Practice Exercises
Create a counter using useState and explain why a global store is unnecessary.
Build a multi-step form using useReducer.
Create a ThemeContext that shares a theme across three screens.
Build a shopping cart using useReducer or Zustand.
Implement the same small counter using Redux Toolkit and compare the setup.
Fetch a product list with TanStack Query and implement retry behavior.
Persist a non-sensitive theme preference between application launches.
Write reducer tests for add, remove, and reset actions.
Implement logout behavior that clears user-specific state and cached information.
Draw a state ownership diagram for a social media application.
Explain which information belongs in navigation state, local state, and server state.
Measure whether a state management change actually improves rendering performance.
22. Interview Questions and Answers
Q1. What is state management?
State management is the process of storing, updating, sharing, and organizing application data that affects behavior and the UI.
Q2. What is the difference between state and props?
State is managed by a component or state system and can change over time. Props are values passed from a parent component to a child.
Q3. What is local state?
Local state is information needed by a single component or a small, closely related part of the component tree.
Q4. What is global state?
Global state is shared information that is needed across different components or screens.
Q5. When should useState be used?
It is appropriate for simple local state, such as input values, visibility flags, and small UI interactions.
Q6. When should useReducer be used?
It is useful when state updates involve related values or multiple defined transitions.
Q7. What is Context API?
Context allows values to be shared with descendant components without manually passing props through every intermediate component.
Q8. Is Context API a complete replacement for Redux?
Not necessarily. Context shares values through a component tree, while Redux Toolkit provides a structured store and related tools for shared client state and debugging.
Q9. What is Redux Toolkit?
Redux Toolkit is the recommended standard approach for writing Redux logic with utilities that simplify store configuration, reducers, actions, and asynchronous workflows.
Q10. What is a Redux slice?
A slice defines a section of Redux state along with the reducers and actions that update it.
Q11. What is Zustand?
Zustand is a lightweight state management library that exposes shared state through a store and hooks.
Q12. What is server state?
Server state is data owned by a remote service and may become stale or change independently of the mobile app.
Q13. What is TanStack Query used for?
It manages server-state fetching, caching, refetching, request status, and cache invalidation.
Q14. What is the difference between client state and server state?
Client state belongs to the application's local interactions, while server state originates from an external system and needs synchronization with that system.
Q15. Does useState automatically persist data?
No. useState manages in-memory state. Persistence requires a separate storage strategy.
Q16. What is immutable state updating?
It means producing new objects or arrays for changed state rather than modifying existing state directly.
Q17. What are selectors?
Selectors retrieve particular values from a state store so components can subscribe to the data they need.
Q18. Why should derived values often not be stored separately?
They can be calculated from source state, avoiding duplicated information that could become inconsistent.
Q19. How should authentication state be managed?
Use a suitable shared state approach for session status and user information, store credentials securely, and enforce access control on the server.
Q20. How should a shopping cart be managed?
Use a predictable reducer or shared store when several screens need it, and derive totals from cart items when practical.
Q21. What is state persistence?
Persistence saves selected information outside in-memory state so it can be restored later.
Q22. Why should sensitive tokens not be stored in AsyncStorage?
AsyncStorage is unencrypted and is not designed for secret credential storage.
Q23. How can unnecessary re-renders be reduced?
Keep state close to where it is used, select only required store values, split unrelated contexts when appropriate, and profile before optimizing.
Q24. Should navigation state be stored in Redux?
Usually, the navigation library should own navigation state. Duplicating it in Redux is unnecessary unless a specific integration requirement justifies it.
Q25. How do you choose the right state management approach?
Identify the state category, consumers, update complexity, server-data requirements, persistence needs, performance constraints, and debugging needs. Choose the simplest approach that satisfies those requirements.
23. Quick Revision Checklist
State is information that can change and affect application behavior.
Use useState for simple local state.
Use useReducer for related state transitions.
Use Context API to share values through a component tree.
Use Redux Toolkit for structured shared client state when justified.
Consider Zustand for a lightweight shared store.
Use TanStack Query or RTK Query for appropriate server-state requirements.
Keep navigation state with the navigation system in most cases.
Use persistent storage only for information that needs to survive restarts.
Never treat unencrypted storage as secure credential storage.
Keep state updates predictable and avoid direct mutation.
Test state transitions, API failures, persistence, and logout cleanup.
Choose tools based on requirements rather than popularity alone.
Choosing the right state management approach is an important part of building maintainable React Native applications. Start by identifying whether the information is local UI state, shared client state, remote server data, navigation state, or persistent data.
Use React's built-in hooks for simple and well-contained state. Consider Context API for values shared across a component tree, Redux Toolkit or Zustand for more complex shared client state, and TanStack Query or RTK Query for server-state requirements. Persistence should be designed separately, with secure handling for sensitive information.
The most effective architecture is not necessarily the one with the most libraries. It is the one that makes state ownership clear, updates predictable, data secure, and the application easy to maintain as its requirements grow.